#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace KLib.Physics.Collisions
{
#if !XBOX
    [System.ComponentModel.TypeConverter(typeof(CustomPropery<Grid>))]
#endif
    public class Grid
    {
        #region Fields
        private float cellSize;
        private float inverseCellSize;
        private AABB bounds;
        float[,] nodes;
        float[] pointVals;
        Vector2[] points;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the points on the Grid
        /// </summary>
        public Vector2[] Points
        {
            get { return points; }
        }

        public float[] PointValues
        {
            get { return pointVals; }
        }
        #endregion

        #region Initialization

        /// <summary>
        /// Creates an Empty Grid
        /// </summary>
        public Grid() 
        {
            cellSize = 0;
            inverseCellSize = 0;
            bounds = new AABB();
            nodes = new float[0,0];
            points = new Vector2[0];
        }

        /// <summary>
        /// Creates a Grid that covers the entire geometry.
        /// </summary>
        /// <param name="geometry">The Geometry to build the Grid around</param>
        /// <param name="cellSize">The size of each cell in the grid. The higher this is, the more accurate the collision will be, at the cost of preformance</param>
        public void CreateGrid(Geom geometry, float cellSize)
        {
            //geometry.Update();

            Matrix tmp = geometry.Matrix;
            geometry.Matrix = Matrix.Identity;
            geometry.Update();
            bounds = new AABB(geometry.Bounds);

            this.cellSize = cellSize;
            this.inverseCellSize = 1 / cellSize;

            int xSize = (int)Math.Ceiling(Convert.ToDouble((bounds.Max.X - bounds.Min.X) * inverseCellSize)) + 1;
            int ySize = (int)Math.Ceiling(Convert.ToDouble((bounds.Max.Y - bounds.Min.Y) * inverseCellSize)) + 1;

            this.nodes = new float[xSize, ySize];
            this.pointVals = new float[xSize * ySize];
            this.points = new Vector2[xSize * ySize];

            int count = 0;
            Vector2 vector = bounds.Min;
            for (int x = 0; x < xSize; ++x, vector.X += cellSize)
            {
                vector.Y = bounds.Min.Y;
                for (int y = 0; y < ySize; ++y, vector.Y += cellSize)
                {
                    nodes[x, y] = geometry.GetNearestDistance(vector);
                    points[count] = vector;
                    pointVals[count] = nodes[x,y];
                    count++;
                }
            }
            geometry.Matrix = tmp;
        }

        /// <summary>
        /// Creates a Copy of another grid.
        /// </summary>
        /// <param name="grid">The Grid to Copy</param>
        public Grid(Grid grid)
        {
            this.cellSize = grid.cellSize;
            this.inverseCellSize = grid.inverseCellSize;
            this.bounds = grid.bounds;
            this.nodes = (float[,])grid.nodes.Clone();
            this.points = (Vector2[])grid.points.Clone();
        }
        #endregion

        #region Methods

        #endregion

        #region Helper Methods
        /// <summary>
        /// Checks to see if an Intersection has occured.
        /// </summary>
        /// <param name="vector">The position to check if the intersection has occured with.</param>
        /// <param name="feature">The feature that will store the intersection data</param>
        /// <returns>True if an intersection occured, false if no intersection occured.</returns>
        public bool Intersect(ref Vector2 vector, out Feature feature)
        {
            if (bounds.Contains(vector))
            {
                int x = (int)Math.Floor((vector.X - bounds.Min.X) * inverseCellSize);
                int y = (int)Math.Floor((vector.Y - bounds.Min.Y) * inverseCellSize);

                float xPercent = (vector.X - (cellSize * x + bounds.Min.X)) * inverseCellSize;
                float yPercent = (vector.Y - (cellSize * y + bounds.Min.Y)) * inverseCellSize;

                float bottomLeft = nodes[x, y];
                float bottomRight = nodes[x + 1, y];
                float topLeft = nodes[x, y + 1];
                float topRight = nodes[x + 1, y + 1];

                if(bottomLeft <= 0
                    || bottomRight <= 0
                    || topLeft <= 0
                    || topRight <= 0)
                {
                    float top = MathHelper.Lerp(topLeft,topRight,xPercent);
                    float bottom = MathHelper.Lerp(bottomLeft, bottomRight, xPercent);
                    float distance = MathHelper.Lerp(bottom,top,yPercent);

                    if (distance <= 0)
                    {
                        float left = MathHelper.Lerp(bottomLeft, topLeft, yPercent);
                        float right = MathHelper.Lerp(bottomRight, topRight, yPercent);

                        Vector2 normal = Vector2.Zero;
                        normal.X = right - left;
                        normal.Y = top - bottom;
                        Vector2.Normalize(ref normal, out normal);

                        feature = new Feature(vector, normal, distance);
                        return true;
                    }
                }
            }
            feature = new Feature();
            return false;
        }
        #endregion
    }
}
